package net.caiyixiu.liaoji.common.groupAdapter

import androidx.annotation.MainThread
import androidx.recyclerview.widget.DiffUtil
import androidx.recyclerview.widget.RecyclerView
import com.xiebishe.mylibrary.common.groupAdapter.ThreadUtils
import kotlinx.coroutines.*
import kotlinx.coroutines.sync.Mutex
import kotlinx.coroutines.sync.withLock
import java.util.*

private val weakMap: WeakHashMap<RecyclerView.Adapter<*>, InCallback<*, *, *>> = WeakHashMap()

class InCallback<T, VH : RecyclerView.ViewHolder, A : RecyclerView.Adapter<VH>>(val adapter: A, originList: List<T>) : DiffUtil.Callback() {
    val mutex = Mutex()
    var scope: CoroutineScope? = null
    val originList: MutableList<T> = ArrayList(originList)
    lateinit var itemSame: (a: T, b: T) -> Boolean
    lateinit var contentSame: (a: T, b: T) -> Boolean
    var switchData: ((A, resultList: List<T>) -> Unit)? = null
    var payLoadCreate: ((o: T, n: T) -> Any?)? = null
    lateinit var toList: List<T>
    override fun areItemsTheSame(oldItemPosition: Int, newItemPosition: Int): Boolean {
        val o = originList[oldItemPosition]
        val r = toList[newItemPosition]
        return itemSame(o, r)
    }

    override fun getOldListSize(): Int {
        return originList.size
    }

    override fun getNewListSize(): Int {
        return toList.size
    }

    override fun areContentsTheSame(oldItemPosition: Int, newItemPosition: Int): Boolean {
        val o = originList[oldItemPosition]
        val r = toList[newItemPosition]
        return contentSame(o, r)
    }

    override fun getChangePayload(oldItemPosition: Int, newItemPosition: Int): Any? {
        payLoadCreate?.let {
            val o = originList[oldItemPosition]
            val r = toList[newItemPosition]
            return it(o, r)
        }
        return null
    }
}

class DiffBuilder<T, VH : RecyclerView.ViewHolder, A : RecyclerView.Adapter<VH>>(private val inCallback: InCallback<T, VH, A>) {
    /**
     * 用于计算两个item 是否代表同一个东西，用于计算add，insert，move，remove
     */

    @MainThread
    fun itemSame(itemSame: (a: T, b: T) -> Boolean): DiffBuilder<T, VH, A> {
        ThreadUtils.AssertMainThread("itemSame")
        val callback = inCallback
        callback.itemSame = itemSame
        return this

    }

    /**
     * 如果itemSame 为true ，那么两个item代表同一个东西，用这个方法计算内容是否变化，用于触发 itemchanged
     */
    @MainThread
    fun contentSame(contentSame: (a: T, b: T) -> Boolean): DiffBuilder<T, VH, A> {
        ThreadUtils.AssertMainThread("itemSame")
        val callback = inCallback
        callback.contentSame = contentSame
        return this

    }

    /**
     * 如果itemSame 为true，contentSame为false ，用于生成payload，可以不传
     */

    @MainThread
    fun payLoadCreate(payLoadCreate: (o: T, n: T) -> Any?): DiffBuilder<T, VH, A> {
        ThreadUtils.AssertMainThread("itemSame")
        val callback = inCallback
        callback.payLoadCreate = payLoadCreate
        return this

    }

    /**
     * 如果使用异步的方式计算，需要将adapter数据变化的代码传入进来，在apply时调用这个方法，如果使用同步方式计算，这个方法可以不传，但是需要在同步调用前后自己处理adapter的数据变化
     * <b>无论是否用switchData还是自己处理，一定不要使用notifyDataChange或者其他相关方法</b>
     */
    @MainThread
    fun switchData(switchData: (a: A, resultList: List<T>) -> Unit): DiffBuilder<T, VH, A> {
        ThreadUtils.AssertMainThread("itemSame")
        val callback = inCallback
        callback.switchData = switchData
        return this

    }

    /**
     * 如果使用异步的方式计算，可以在传入CoroutineScope用于生命周期结束时自动取消
     */

    @MainThread
    fun watch(scope: CoroutineScope): DiffBuilder<T, VH, A> {
        ThreadUtils.AssertMainThread("itemSame")
        val callback = inCallback
        callback.scope = scope
        return this

    }

    fun applyDifferSync(toList: List<T>, detectMove: Boolean = false): A {
        return inCallback.adapter.applyDifferSync(toList, detectMove)
    }

    fun applyDifferAsync(toList: List<T>, detectMove: Boolean = false): A {
        return inCallback.adapter.applyDifferAsync(toList, detectMove)
    }
}

/**
 * 初始化
 * @param originList 原始数据，这个列表数据会被复制入
 */
@MainThread
fun <T, VH : RecyclerView.ViewHolder, A : RecyclerView.Adapter<VH>> A.initDiffer(originList: List<T>): DiffBuilder<T, VH, A> {
    ThreadUtils.AssertMainThread("initDiffer")
    @Suppress("UNCHECKED_CAST")
    val callback: InCallback<T, VH, A> = weakMap[this] as InCallback<T, VH, A>?
            ?: let {
                val callback = InCallback(this, originList)
                weakMap[this] = callback
                callback
            }

    return DiffBuilder(callback)

}


@MainThread
fun <T, VH : RecyclerView.ViewHolder, A : RecyclerView.Adapter<VH>> A.applyDifferSync(toList: List<T>?, detectMove: Boolean = false): A {
    ThreadUtils.AssertMainThread("applyDiffer")
    @Suppress("UNCHECKED_CAST")
    val callback: InCallback<T, VH, A> = weakMap[this] as InCallback<T, VH, A>
    callback.toList = toList ?: emptyList()
    val calculateDiff = DiffUtil.calculateDiff(callback, detectMove)
    callback.switchData?.invoke(this, toList ?: emptyList())
    callback.originList.clear()
    callback.originList.addAll(toList ?: emptyList())
    calculateDiff.dispatchUpdatesTo(this)
    return this
}

@MainThread
fun <T, VH : RecyclerView.ViewHolder, A : RecyclerView.Adapter<VH>> A.applyDifferAsync(toList: List<T>?, detectMove: Boolean = false): A {
    ThreadUtils.AssertMainThread("applyDiffer")
    @Suppress("UNCHECKED_CAST")
    val callback: InCallback<T, VH, A> = weakMap[this] as InCallback<T, VH, A>
    (callback.scope ?: GlobalScope).launch(Dispatchers.Default) {
        callback.mutex.withLock {
            callback.toList = toList ?: emptyList()
            val calculateDiff = DiffUtil.calculateDiff(callback, detectMove)
            withContext(Dispatchers.Main) {
                callback.switchData!!(this@applyDifferAsync, toList ?: emptyList())
                callback.originList.clear()
                callback.originList.addAll(toList ?: emptyList())
                calculateDiff.dispatchUpdatesTo(this@applyDifferAsync)
            }
        }
    }
    return this
}